package org.gendut.func;

// !Chunk for Lazy-Evalution
/* <literate> */
/**
 * This chunk holds and controls lazy-evaluated values. An instance of this
 * class is partially thread-safe: It is possible that multiple threads access a
 * lazy object concurrently, but for efficiency reasons, no provision is made
 * that prevents from evaluating the function more than once. However, a thread
 * won't reevaluate if it can see the result of a previous evaluation, and
 * processor caches will be synchronized in such way that only complete results
 * become visible. As an example, Concurrent Haskell has simililar behavior.
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class LazyValue<T> {
  /*
   * Define a marker object for black holes. black holes are place holders for
   * unevaluated objects.
   */
  private final static Object blackhole = new Object();

  /*
   * An unevaluated lazy value consists of a function and the argument it
   * applies to:
   */
  private volatile Function f;

  private volatile Object arg;

  private volatile Throwable exception;

  /**
   * The constructor takes a function and an argument. Both are applied when the
   * value is accessed the first time.
   */
  public <A> LazyValue(Function<? super A, ? extends T> f, A arg) {
    if (f == null) {
      throw new NullPointerException();
    }
    this.f = f;
    this.arg = arg;
  }

  /*
   * We initialize the result as a black hole. The result is a <i>volatile</i>
   * object in order to synchronize processor caches. Note that this is a result
   * of Java's memory model and does not translate to C++, though similar
   * keywords exist there.
   */
  private volatile Object result = blackhole;

  /**
   * Computes the value.
   */
  final public T value() {
    if (result == blackhole)
      synchronized (this) {
        if (result == blackhole) {
          if (exception != null)
            doThrow(exception);
          Function fun = f;
          Object argument = arg;
          f = null;
          arg = null;
          try {
            result = fun.get(argument);
          } catch (Throwable e) {
            exception = e;
            doThrow(e);
          }
        }// `if`
      }// `synchronized`
    f = null;
    arg = null;
    return (T) result;
  }

  private static void doThrow(Throwable e) {
    if (e instanceof Error)
      throw (Error) e;
    else if (e instanceof RuntimeException)
      throw (RuntimeException) e;
    else
      throw new IllegalStateException("Unknown exception type", e);

  }

  /**
   * Returns true if the value is still blackholed.
   */
  final public boolean isBlackhole() {
    return result == blackhole;
  }
  /*
   * Note: One might be tempted to use blackholing for detecting cycles during
   * evaluation. This, however, does only work in single-threaded use.
   */
}// `class`
